Repository navigation
Port extensibility customization guide to XTravels and add CDS-Oyster documentation and reference - #2917
Port extensibility customization guide to XTravels and add CDS-Oyster documentation and reference#2917nkaputnik wants to merge 31 commits into
Conversation
Port the extensibility customization guide from the Orders/bookshop sample to XTravels: model-only extension (x_priority + x_CostCenter), multi-repo workspace setup, local multitenancy, and tenant subscription. Regenerate all four screenshots against a running XTravels tenant: - base Travels list - extension project readme - Fiori preview with Priority and Cost Center columns - deployed tenant UI with Priority and Cost Center columns Remove the superseded Orders screenshots.
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
- Add Preferred Project Layout, Ship a Pre-Filled Base Model, and Make It Browsable subsections so a cloned template runs with npm install + cds watch. - Add Seed External & Federated Data (@capire/common, S4 customers, flights): explain the minified base model, trim sap.common-Currencies.csv to code;symbol;name;descr, and don't seed Languages/Regions (no target table). - Split reference wiring into its own section (Provide Reference Logic for Local Runs); renumber Extension Guides/Deploy accordingly. - Purge comments from JSON code blocks; use VitePress line-highlight instead. - Regenerate xtravels-deployed-ext and xtravels-ext-readme screenshots.
Extract the @sap/cds-oyster code-extension sandbox reference material into a dedicated Code Extension Reference page (code-extension.md), mirroring the multitenancy index.md/mtxs.md how+reference split. business-logic.md and business-logic-advanced.md keep their walkthroughs and best practices, with moved blocks replaced by short orientation stubs linking into the reference. Also applies a house-style pass across all three pages (pre-defined, -ize spelling, bare ## Introduction headings, no & in headings) and adds the new page to _menu.md.
…ox debugging business-logic.md: - Document the generated JSDoc handler stub and how it drives code completion - Note the mocked sandbox is debuggable in-process (cds watch --debug) business-logic-advanced.md: - Reframe before-CREATE around the verified declarative-vs-handler boundary: @mandatory/@assert.range/@assert.notNull/@readonly work in extensions only on added, defaulted fields; provider fields and @Assert:(case ...) constraints are rejected at push. Keep only the cross-record checks (customer lookup, budget). - Replace the hand-rolled TravelLog audit trio with a pointer to @cap-js/change-tracking and a single after-CREATE emit('TravelCreated') example - Swap TravelLog for CustomerPolicies + a TravelCreated event in the opened surface code-extension.md: - Add a Debugging in the mocked sandbox section and a troubleshooting row
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
Control PanelHi, I'm an AI-powered Review Bot that helps you with summarizing and reviewing pull requests.
|
SummaryThe following content is AI-generated and provides a summary of the pull request: Document XTravels Extensibility and CDS-Oyster Code ExtensionsDocumentation📚 Migrates the SaaS customization walkthrough to the modular XTravels sample and introduces comprehensive guidance for sandboxed business logic extensions with Changes
Images and Links
PR Bot InformationVersion:
|
There was a problem hiding this comment.
I found a few substantive documentation issues that could mislead readers about CAP handler transaction timing and one template layout mismatch around db/data. The rest of the PR reads as a coherent port to XTravels with clear structure and useful examples.
PR Bot Information
Version: 1.31.64
- LLM:
gpt-5.5 - File Content Strategy: Full file content
- Event Trigger:
issue_comment.edited - Review Focus Files:
- Correlation ID:
f0a55c10-bcd9-11f1-8252-55e0b9ef2f35
…layout after handlers run inside the request transaction (pre-commit), not post-commit: a reject rolls back both the write and the handler's own changes. Verified via a purpose-built test in cds-oyster under both the mocked and wasm engines. Also show db/data in the template layout, which the walkthrough creates for the shipped code list.
There was a problem hiding this comment.
I posted a few targeted comments where the new docs could mislead readers about supported handler events or local test setup. The earlier transaction-boundary and template-layout comments appear to have been addressed, so I did not repeat them.
PR Bot Information
Version: 1.31.64
- LLM:
gpt-5.5 - Correlation ID:
288c5390-bcdf-11f1-92bb-b6b30f554ebc - Event Trigger:
issue_comment.edited - Review Focus Files:
- File Content Strategy: Full file content
Resolve hyperspace-bot and reviewer comments on the extensibility guide: - Correct the CRUD handler event scope: no before-READ; before covers Create/Update/Upsert/Delete, after covers all CRUD+Upsert. Fixed the prose in business-logic-advanced.md and the handler-file tree in code-extension.md (verified against cds-oyster registrationCheck.js). - Show the inactive stub filename with its leading # in business-logic.md. - Clarify local submitForReview wiring vs the reference-logic pattern. - Rework the Add Test Data / seeding section in customization.md: say explicitly to copy the base app's CSVs and from where, turn the federated-data paths into a table, note cds pull only writes .base/, and explain why flights data comes from @capire/xflights-data (the integration package) rather than the @capire/xflights microservice. - Tighten the modular-application warning.
- Reframe the allowlist note: extending reuse/federated entities is a deliberate allowlist scoping choice, not a framework limitation. Verified against the real base model with the mtxs push-time linter: extending a @federated entity (sap.capire.s4.Customers) is rejected only because it is not in the allowlist, and is accepted once added. Focus the note on why the reuse modules are deliberately left out. - Replace 'seed/seeding' with capire's established 'test data' / 'initial data' vocabulary; rename the subsection anchor accordingly.
| namespace x_orders.ext; // only applies to new entities defined below | ||
| using { OrdersService, sap.capire.orders.Orders } from '@capire/orders'; | ||
| namespace x_travels.ext; // only applies to new entities defined below | ||
| using { TravelService, sap.capire.travels.Travels } from '@capire/xtravels'; |
There was a problem hiding this comment.
Do we miss an as here? Or would we need to full namespace for the following extend statement?
| using { TravelService, sap.capire.travels.Travels } from '@capire/xtravels'; | |
| using { TravelService, sap.capire.travels.Travels as Travels} from '@capire/xtravels'; |
Validated customization + business-logic + business-logic-advanced against a
from-scratch XTravels run (clone → MT enable → subscribe → pull → push).
customization.md:
- app UI port 4004 → 4114 (XTravels pins 4114)
- create the extension project OUTSIDE the base-app npm workspace (cd ../..),
else @capire/xtravels resolves to the base-app sources and it won't compile;
made the rationale a note
- fix test-data cp paths to ../samples/{xtravels/db/data, common/db/data,
s4/srv/external/data, xflights/apis/data-service/data}
- cds add extension only scaffolds extends + .base/package.json (made the
bookshop placeholder an info note; dropped the false app/extension.cds claim)
- inline the sidecar/app cds watch start commands at Subscribe
- note to move @cap-js/sqlite to optionalDependencies
business-logic.md / code-extension.md:
- cds watch --wasm → cds watch --profile wasm (the --wasm flag is not recognized)
- show extension_points() being called from init()
- cds push → cds push --to <sidecar> -u bob:
- note: upgrade tenants subscribed before code extensibility was enabled
- note: verify with a user authorized for the action, not the ExtensionDeveloper
business-logic-advanced.md:
- keep element-prefix; prefix all extension fields AND entities AND the event
with x_ (x_customerStatus, x_CustomerBudgets, x_CustomerPolicies, x_TravelCreated)
- new fields extend the base entity (extend Travels), not the TravelService
projection
Document the cds-oyster security concept: WASM-enforced tenant isolation, a mocked CDS API, callback-only integration with the runtime (no shared memory, no breakout), and runtime-secured service-fence enforcement for queries. Preface the forbidden-constructs linter reference as a push-time hygiene layer on top of that model.
Document two governance practices for subscribers running code extensions: separate the handler author from the designated deployer (four-eyes review via the cds.ExtensionDeveloper role), and validate on a test tenant before promoting to production.
Add a Technical Roles reference section to the MTX guide covering cds.ExtensionDeveloper and cds.Subscriber (verified against @sap/cds-mtxs), the mtcallback and internal-user technical roles, and guidance on when each is needed. Link it from the extension-tenant setup step in the customization guide.
| | Role | Unlocks | Used for | | ||
| | ---- | ------- | -------- | | ||
| | `cds.ExtensionDeveloper` | [ExtensibilityService](#extensibilityservice) (`/-/cds/extensibility/**`) | Reading the base model and pushing, activating, and deleting a tenant's own extensions (both model and code extensions). This is the role that `cds pull` and `cds push` require. It is **tenant-scoped**: a holder may only manage extensions for the tenant they belong to. | | ||
| | `cds.Subscriber` | [DeploymentService](#deploymentservice) (`/-/cds/deployment/**`), [SaasProvisioningService](#saasprovisioningservice) (`/-/cds/saas-provisioning/**`) | Subscribing, upgrading, and unsubscribing tenants. A technical role for the provisioning client (the automation that onboards tenants), never for end users. | |
There was a problem hiding this comment.
I think this is never used productively, only for the local testing scenario. When deployed, only 'internal-user' is used.
There was a problem hiding this comment.
You mean the subcriber role?
There was a problem hiding this comment.
Good catch, thanks. You're right that cds.Subscriber isn't the productive path. I verified against @sap/cds-mtxs: it's declared on DeploymentService (['cds.Subscriber','internal-user']) and SaasProvisioningService (['cds.Subscriber','mtcallback','internal-user']), but in a deployed app those endpoints are reached as internal-user from the application zone, with SaaS onboarding/offboarding via mtcallback. I've reworded the table and the when-to-use guidance to frame cds.Subscriber as mostly a local-testing affordance that's rarely assigned productively (a941bd9). I kept mtcallback alongside internal-user as productive callers for SaaS provisioning, since the service model accepts it there.
| - `mtcallback`: the role the SAP BTP SaaS Provisioning service presents when it calls the [SaasProvisioningService](#saasprovisioningservice) callback during tenant onboarding and offboarding. | ||
| - `internal-user`: a pseudo-role for internal, technical communication inside the [application zone](../security/overview#application-zone), for example the main application calling its own MTX sidecar. It is established by platform trust (a shared XSUAA instance), not granted to users. | ||
|
|
||
| ::: warning Keep technical roles out of business roles |
Per review feedback: cds.Subscriber is declared on the Deployment and SaaS-provisioning services but is not the productive caller. In a deployed app those endpoints are reached as internal-user from the application zone, with SaaS onboarding/offboarding via mtcallback. Reword the role table and the when-to-use guidance accordingly.
Add an Extension Lifecycle and Schema Changes section covering additive model merges, activation, removing/shrinking extensions (local SQLite error vs HANA orphans), type changes, canonical column-name reuse, and the relevant config (auto_undeploy, undeploy.json, skipExtensionCheck, schema_evolution). Link it from the activation step in the customization guide.
|
|
||
| ::: | ||
|
|
||
| Activating an extension changes the tenant's database schema. Model extensions are **additive**: later pushes add fields and entities, but removing or retyping an activated element is constrained, and on SAP HANA removed columns are not dropped. See [Extension Lifecycle and Schema Changes](../multitenancy/mtxs#extension-lifecycle) before you rely on changing or rolling one back. |
There was a problem hiding this comment.
It is more complicated. Sqlite will throw an error. With HANA, fields are dropped (!). Entities are not dropped except undeploy.json contains a matching pattern or auto_undeploy is set.
…guide-to-xtravels' into port-extensibility-guide-to-xtravels
HDI's delta deployment works at the file level: deleted files are not undeployed by default, but modified files are redeployed with ALTER. That means removing a field from an extension drops the column and its data on HANA, while removing a whole entity leaves the table behind as an orphan (only cleared via undeploy.json or auto_undeploy).
…omer flow, oyster sidecar fix customization.md: add 'Set Up the Extension Project Locally' (sidecar :4005, host :4114, extension project standalone :4006) to the provider walkthrough; rewrite the SaaS Customer section BTP-first and renumber; de-noise Prerequisites and demote incidental links; refocus 'Provide Extension Guides' on app-specific content; fix BTP 'cds pull' syntax. business-logic.md: point Prerequisites at the provider walkthrough; move the local srv/server.js reference wiring into Test-Drive before the 409 check; declare @sap/cds-oyster in mtx/sidecar/package.json instead of 'npm --prefix' and warn against npm in the sidecar (dual-cds); add a re-subscribe-tenants-after-oyster warning. Remove em-dashes throughout both guides.
renejeglinsky
left a comment
There was a problem hiding this comment.
I ended up with a running extension. 👍 For things I wondered about during that test, I added the following comments.
| It also writes a small **bookshop** placeholder that you don't keep: it sets `extends` to `@capire/bookshop` and writes `.base/package.json` as `{ "name": "@capire/bookshop" }`. You don't hand-edit the latter: point `extends` at your app (next step), and [`cds pull`](#pull-base) overwrites `.base/` with your real base model, so its name becomes `@capire/xtravels` on its own. | ||
| ::: | ||
|
|
||
| 2. `cds add extension` sets `extends` to a placeholder, point it at your SaaS app and add the two focused settings below to your _package.json_: | ||
|
|
||
| ::: code-group | ||
|
|
||
| ```jsonc [package.json] | ||
| ```json{3,6,9} [package.json] | ||
| { | ||
| "name": "@capire/orders-ext", | ||
| "extends": "@capire/orders", | ||
| "workspaces": [ ".base" ] | ||
| "name": "xtravels-ext", | ||
| "extends": "@capire/xtravels", | ||
| "workspaces": [ ".base" ], | ||
| "optionalDependencies": { | ||
| "@cap-js/sqlite": "^3" |
There was a problem hiding this comment.
Should I edit the bookshop placeholder ins Step2 or not? Step 1 says: Do not do that manually. What is true?
There was a problem hiding this comment.
When I tried it, cds pull did not change the extends property. So I read again and found that we should be editing the xtravels-ext root package.json as described in the following. What we don't edit is the .base/package.json which gets updated by cds pull.
| - `workspaces` is a list of folders including the one where the base model is stored. `cds pull` will add this property automatically if not already present. | ||
| - `extends` is the name by which the extension model will refer to the base model. `cds add extension` writes a placeholder here, change it to your SaaS app (`@capire/xtravels`). It must be a valid npm package name as it will be used by `cds pull` as a package name for the base model. It doesn't have to be a unique name, nor does it have to exist in a package registry like npmjs, as it will only be used locally. | ||
| - `workspaces` (added by `cds add extension`) is a list of folders including the one where the base model is stored. `cds pull` will keep this property in sync if needed. | ||
| - `@cap-js/sqlite` provides the in-memory database for local `cds watch`. `cds add extension` scaffolds it under `devDependencies`, **move it to `optionalDependencies`** as shown above. It sits there **on purpose**: it's still installed by `npm install`, so local test-drives get a real database, but because it isn't a regular `dependency` or `devDependency`, `cds build` doesn't treat the project as a runnable app and therefore keeps your local `test/data` **out of the pushed extension** (see [Add Test Data](#add-test-data)). Leaving it as a `devDependency` would bundle that dev-only data into `cds push` and deploy it to the tenant. |
There was a problem hiding this comment.
I think it's now an optional dependency by default. No need to move it.
| - `extends` is the name by which the extension model will refer to the base model. `cds add extension` writes a placeholder here, change it to your SaaS app (`@capire/xtravels`). It must be a valid npm package name as it will be used by `cds pull` as a package name for the base model. It doesn't have to be a unique name, nor does it have to exist in a package registry like npmjs, as it will only be used locally. | ||
| - `workspaces` (added by `cds add extension`) is a list of folders including the one where the base model is stored. `cds pull` will keep this property in sync if needed. | ||
| - `@cap-js/sqlite` provides the in-memory database for local `cds watch`. `cds add extension` scaffolds it under `devDependencies`, **move it to `optionalDependencies`** as shown above. It sits there **on purpose**: it's still installed by `npm install`, so local test-drives get a real database, but because it isn't a regular `dependency` or `devDependency`, `cds build` doesn't treat the project as a runnable app and therefore keeps your local `test/data` **out of the pushed extension** (see [Add Test Data](#add-test-data)). Leaving it as a `devDependency` would bundle that dev-only data into `cds push` and deploy it to the tenant. | ||
| - `cds.server.port` fixes the local test-drive port to `4006`, so extension developers can just run `cds watch` without passing `--port`. |
| It also writes a small **bookshop** placeholder that you don't keep: it sets `extends` to `@capire/bookshop` and writes `.base/package.json` as `{ "name": "@capire/bookshop" }`. You don't hand-edit the latter: point `extends` at your app (next step), and [`cds pull`](#pull-base) overwrites `.base/` with your real base model, so its name becomes `@capire/xtravels` on its own. | ||
| ::: | ||
|
|
||
| 2. `cds add extension` sets `extends` to a placeholder, point it at your SaaS app and add the two focused settings below to your _package.json_: |
There was a problem hiding this comment.
The whole step 2 is a mixture of what is done and what needs to be done. That is confusing. If this can be reduced to "what is done" (see my other comments) than this can be a detail box but not a step.
| So the local setup is up to three processes, in three terminals, started in this order. | ||
|
|
||
| **1. Start the MTX sidecar first.** From the sidecar subfolder, with no flags: | ||
|
|
||
| ```sh | ||
| cd mtx/sidecar | ||
| cds watch | ||
| ``` | ||
|
|
||
| The sidecar serves the subscription, deployment, and extensibility endpoints on **:4005**. Leave it running. | ||
|
|
||
| **2. Start the XTravels host.** In a second terminal, from the `xtravels/` root: | ||
|
|
||
| ```sh | ||
| cds watch --with-mtx | ||
| ``` |
There was a problem hiding this comment.
I think we're missing a third terminal. Or is it not needed?
There was a problem hiding this comment.
Is the next section the third terminal? Or is the terminal in xtravels-ext the third terminal?
| ::: | ||
|
|
||
| ::: tip Provide a complete set of test data | ||
| A bare `Travels` table isn't browsable on its own: its associations point at agencies, currencies, customers, and flights that aren't loaded, so the Fiori list shows blank cells. To make the template **browsable out of the box**, copy the base app's `.csv` files into your extension's `test/data/` folder. |
There was a problem hiding this comment.
In my case I had to create a test/data folder before I run the copy commands.
| cp ../samples/xtravels/db/data/sap.capire.travels-{Travels,Bookings,Bookings.Supplements,TravelAgencies,TravelStatus,TravelStatus.texts,TravelPurposes,PaymentMethods,BookingStatus,BookingStatus.texts}.csv test/data/ | ||
|
|
||
| # Reuse code lists from @capire/common: plain copy (Currencies is handled separately below) | ||
| cp ../samples/common/db/data/sap.common-{Countries,Countries_texts,Currencies_texts}.csv test/data/ |
There was a problem hiding this comment.
Currencies is missing:
| cp ../samples/common/db/data/sap.common-{Countries,Countries_texts,Currencies_texts}.csv test/data/ | |
| cp ../samples/common/db/data/sap.common-{Countries,Countries_texts,Currencies,Currencies_texts}.csv test/data/ |
| USD;$;US Dollar;United States Dollar | ||
| ``` | ||
|
|
||
| - **`Languages` and `Regions`: don't copy them at all.** They aren't in the pulled model, so there's no table to load into; copying `sap.common-Languages.csv` would fail with `no such table`. |
There was a problem hiding this comment.
Maybe this is obsolete or needs to be rephrased. Those tables are not part of the copy commands. I guess this was valid when people went into the original folder and copied manually. Maybe now only a hint that not all tables from the base are needed is enough.
|
|
||
| - **`Languages` and `Regions`: don't copy them at all.** They aren't in the pulled model, so there's no table to load into; copying `sap.common-Languages.csv` would fail with `no such table`. | ||
|
|
||
| The companion `sap.common-Currencies_texts`, `sap.common-Countries`, and `sap.common-Countries_texts` CSVs already match the pulled model, copy them as-is. |
There was a problem hiding this comment.
They are already copied
|
|
||
| #### Make It Browsable {#template-ui} | ||
|
|
||
| For model-only extensions, the [generic Fiori preview](#test-locally) is enough to see your new fields. If you want extension developers to work against the **real** application UI locally, copy the base app's Fiori Elements shell (`app/travels/webapp/`) into the template. `cds watch` then serves it at `http://localhost:4006/travels/webapp/index.html`. The UI annotations themselves already travel with the base model, so only the HTML/JS shell needs to be copied. |
There was a problem hiding this comment.
Maybe add:
mkdir app/travels
cp -r ../samples/xtravels/app/travels/webapp/ app/travels/





Port Extensibility Guides to XTravels
Documentation
📚 Updates the extensibility documentation to use the XTravels sample throughout and adds new code extensibility guides for sandboxed business logic extensions. The model customization walkthrough now focuses on extending
sap.capire.travels.Travelswithx_priorityandx_CostCenter, including local setup, tenant activation, test data guidance, and refreshed UI verification screenshots.Changes
guides/extensibility/customization.md: Reworked the customization guide from Orders/bookshop examples to XTravels, including multi-repo workspace setup, MTX sidecar subscription flow, extension template layout, local reference wiring, model-only extension examples, local vs shipped data guidance, and XTravels-specific UI annotations.guides/extensibility/business-logic.md: Added a new guide for pre-defined business logic extension points using@sap/cds-oyster, covering provider setup, tenant handler implementation, local/wasm testing, push flow, and best practices.guides/extensibility/business-logic-advanced.md: Added advanced guidance for opening regular services to controlled code extensions, including CRUD handlers, after-READ enrichment, cross-record validation, pagination, and safety recommendations.guides/extensibility/code-extension.md: Added a sandbox reference covering engines, configuration, handler file layout, sandbox API, event scope, query constraints, forbidden constructs, quarantine behavior, and troubleshooting.guides/extensibility/_menu.md: Added navigation entries for the new business logic and code extension documentation pages.guides/extensibility/assets/*: Replaced superseded Orders screenshots with new XTravels screenshots for the base Travels list, extension template README, Fiori preview, and deployed tenant UI.Images and Links
PR Bot Information
Version:
1.31.64264dc780-bcda-11f1-810b-d179ffdc6203issue_comment.edited